package com.ruoyi.framework.aspectj.commonservice.interceptor;

import com.ruoyi.framework.aspectj.commonservice.TraceIdContext;
import com.ruoyi.framework.web.domain.server.Sys;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;

import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * @author: silver
 * created:11/21/22
 * description
 */
public class InvocationTraceInterceptor implements MethodInterceptor {
    private static final String MISSED = "-";
    private static final String LOG_TEMPLATE = "code:{}, message:{}, elapsed:{}ms, begin:{}, invoke:{}.{}({})";
    private static final String INPUT_LOG_TEMPLATE = "invoke:{}.{}, input:({})";
    private static final String OUTPUT_LOG_TEMPLATE = "invoke:{}.{}, output:{}";
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    private Logger logger = LoggerFactory.getLogger(InvocationTraceInterceptor.class);
    private boolean unwrapResult = true;
    private boolean logSuccess = false;
    private boolean printResult = false;

    public InvocationTraceInterceptor() {
    }

    public Object invoke(final MethodInvocation invocation) throws Throwable {
        LocalDateTime begin = LocalDateTime.now();
        this.logInput(Level.INFO, invocation);

        Object result;
        try {
            result = invocation.proceed();
        } catch (Exception var11) {
            this.log(Level.ERROR, invocation, begin, "EXCEPTION", var11.getMessage(), var11);
            throw var11;
        } catch (Throwable var12) {
            this.log(Level.ERROR, invocation, begin, "THROWABLE", var12.getMessage(), var12);
            throw var12;
        }

        if (this.logSuccess) {
            this.log(Level.INFO, invocation, begin, "SUCCESS", "成功", (Throwable) null);
        }
        String id = TraceIdContext.get();
        System.out.println(id);
        if (this.printResult) {
            this.logOutput(Level.INFO, invocation, result);
        }

        return result;
    }

    private void log(final Level level, final MethodInvocation invocation, final LocalDateTime beginTime, final Object code, final String message, final Throwable e) {
        String begin = DATE_TIME_FORMATTER.format(beginTime);
        long elapsed = Duration.between(beginTime, LocalDateTime.now()).toMillis();
        Method method = invocation.getMethod();
        String className = method.getDeclaringClass().getSimpleName();
        String methodName = method.getName();
        Object[] arguments = invocation.getArguments();
        String argumentsString = arguments != null && arguments.length > 0 ? "" : (String) Arrays.stream(arguments).map((x) -> {
            String result = "";
            if (ObjectUtils.allNotNull(new Object[]{x})) {
                result = ToStringBuilder.reflectionToString(x, ToStringStyle.SHORT_PREFIX_STYLE);
            } else {
                result = String.valueOf(x);
            }

            return result;
        }).collect(Collectors.joining(", "));
        switch (level) {
            case ERROR:
                this.logger.error("code:{}, message:{}, elapsed:{}ms, begin:{}, invoke:{}.{}({})", new Object[]{code, message, elapsed, begin, className, methodName, argumentsString, e});
                break;
            case WARN:
                this.logger.warn("code:{}, message:{}, elapsed:{}ms, begin:{}, invoke:{}.{}({})", new Object[]{code, message, elapsed, begin, className, methodName, argumentsString, e});
                break;
            case INFO:
                this.logger.info("code:{}, message:{}, elapsed:{}ms, begin:{}, invoke:{}.{}({})", new Object[]{code, message, elapsed, begin, className, methodName, argumentsString, e});
                break;
            case DEBUG:
                this.logger.debug("code:{}, message:{}, elapsed:{}ms, begin:{}, invoke:{}.{}({})", new Object[]{code, message, elapsed, begin, className, methodName, argumentsString, e});
                break;
            case TRACE:
                this.logger.trace("code:{}, message:{}, elapsed:{}ms, begin:{}, invoke:{}.{}({})", new Object[]{code, message, elapsed, begin, className, methodName, argumentsString, e});
        }

    }

    private void logInput(final Level level, final MethodInvocation invocation) {
        Method method = invocation.getMethod();
        String className = method.getDeclaringClass().getSimpleName();
        String methodName = method.getName();
        Object[] arguments = invocation.getArguments();
        String argumentsString = ArrayUtils.isEmpty(arguments) ? "" : ToStringBuilder.reflectionToString(arguments, ToStringStyle.SHORT_PREFIX_STYLE);
        switch (level) {
            case INFO:
                this.logger.info("invoke:{}.{}, input:({})", new Object[]{className, methodName, argumentsString});
            default:
        }
    }

    private void logOutput(final Level level, final MethodInvocation invocation, final Object data) {
        Method method = invocation.getMethod();
        String className = method.getDeclaringClass().getSimpleName();
        String methodName = method.getName();
        String resultString = "";
        if (data != null) {
            resultString = ToStringBuilder.reflectionToString(data, ToStringStyle.SHORT_PREFIX_STYLE);
        }

        resultString = StringUtils.isEmpty(resultString) ? "" : resultString;
        switch (level) {
            case INFO:
                this.logger.info("invoke:{}.{}, output:{}", new Object[]{className, methodName, resultString});
            default:
        }
    }

    public void setLogger(final String logger) {
        this.logger = LoggerFactory.getLogger(logger);
    }

    public void setUnwrapResult(final boolean unwrapResult) {
        this.unwrapResult = unwrapResult;
    }

    public void setLogSuccess(final boolean logSuccess) {
        this.logSuccess = logSuccess;
    }

    public boolean isPrintResult() {
        return this.printResult;
    }

    public void setPrintResult(boolean printResult) {
        this.printResult = printResult;
    }
}
